import ky, { type Options as KyOptions } from "ky";
import { isNil } from "lodash";
import { LOCAL_STORAGE_KEY_TOKEN } from "../misc/dicts";

type HttpResponse<T> = {
  data: T;
};

type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

type QS = Record<string, string | number | boolean | null | undefined>;

type Options = {
  qs?: QS;
  method: Method;
  url: string;
  json?: unknown;
  form?: FormData | URLSearchParams;
  muted?: boolean;
};

export async function request<T>(options: Options): Promise<T> {
  const headers: Record<string, string> = {};

  const token = localStorage.getItem(LOCAL_STORAGE_KEY_TOKEN);
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }

  const qs: QS = options.qs || {};
  const searchParams = new URLSearchParams();
  for (const name in qs) {
    const value = qs[name];
    if (!isNil(value)) {
      searchParams.set(name, `${value}`);
    }
  }

  const kyOptions: KyOptions = {
    method: options.method,
    searchParams,
  };

  if (options.json) {
    kyOptions.json = options.json;
    headers["Content-Type"] = "application/json";
    headers.Accept = "application/json";
  }

  if (options.form) {
    kyOptions.body = options.form;
  }

  if (options.muted) {
    // @ts-expect-error TODO: 处理弹窗
    conf.muted = true;
  }

  kyOptions.headers = headers;
  const resp = ky(options.url, kyOptions);

  // TODO: 更细致的处理
  const json = (await resp.json()) as HttpResponse<T>;
  return json.data;
}

export function get<T>(options: Omit<Options, "method"> | string) {
  if (typeof options === "string") {
    return request<T>({ url: options, method: "GET" });
  }
  return request<T>({ ...options, method: "GET" });
}

export function patch<T>(options: Omit<Options, "method">) {
  return request<T>({ ...options, method: "PATCH" });
}

export function post<T>(options: Omit<Options, "method">) {
  return request<T>({ ...options, method: "POST" });
}

export function put<T>(options: Omit<Options, "method">) {
  return request<T>({ ...options, method: "PUT" });
}

export function del<T>(options: Omit<Options, "method">) {
  return request<T>({ ...options, method: "DELETE" });
}
